/***************************************************************************
 *    
 *      Copyright (c) 2009,2010,2011 KaiTrade LLC (Delaware)
 *                     All Rights Reserved Worldwide
 *
 * STRICTLY PROPRIETARY and CONFIDENTIAL
 *
 * WARNING:  This file is the confidential property of KaiTrade LLC For
 * use only by those with the express written permission and license from
 * KaiTrade LLC.  Unauthorized reproduction, distribution, use or disclosure 
 * of this file or any program (or document) is prohibited. 
 * 
 ***************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace KTASkeleton
{
    /// <summary>
    /// Class to illustrate the basic driver functions, this levers the driver base class provided in the API
    /// you can write a drive by implimenting KaiTrade.Interfaces.Driver
    /// </summary>
    public class KTASkeleton : DriverBase.DriverBase
    {
        /// <summary>
        /// Path to config files
        /// </summary>
        private string m_ConfigPath;

        /// <summary>
        /// We will use a thread to generate random prices to illustrate the use of price publishing and subscription
        /// </summary>
        private Thread m_PXThread;
        private bool m_RunPX = true;
        private Random m_random;

        public KTASkeleton()
        {
            m_Name = "KTA Skeleton";
            m_ID = "KTSKEL";
            m_Tag = "";
            // initialize member data
            m_Parent = null;
            m_random = new Random();

        }

        /// <summary>
        /// This method is called when the main driver is loaded by the KaiTrade system
        /// This is where you should initialize the connection to your exchange or broker API
        /// </summary>
        /// <param name="myPath"></param>
        protected override void DoStart(string myPath)
        {

            try
            {

                m_DriverLog.Info(m_Name + ":Driver Started");

                // You can use the driver state object from the driver base to 
                // access the state(properties for this driver), these properties are downloaded
                // from the internet when the driver starts up.
                if (m_State != null)
                {
                    try
                    {
                        // By convention we pass the Password and ID to access the broker in this xml
                        string userID = "";
                        if (m_State.UserCredential.IsValidUserId)
                        {
                            userID = m_State.UserCredential.UserId;
                        }

                        string passWord = "";
                        if (m_State.UserCredential.IsValidPwd)
                        {
                            passWord = m_State.UserCredential.Pwd;
                        }
                    }
                    catch (Exception myE)
                    {
                        // you may choose to log and throw and exception here
                        // if you require the credentials
                    }
                }


                // Add products
                // you will normally make a call to the
                // broker API to get their products. Since these calls can be lengthy they should be made on another thread
                // the code below simply shows you how to add the product data to kaitrade.
                addProducts();

                


                // You may want to add some accounts that the user can use
                AddAccount("JSMITH", "Skeleton JSMITH", "KAI");
                AddAccount("MGiles", "Skeleton MGiles", "KAI");
                AddAccount("MWTrader", "Skeleton MWTrader", "KAIB");
                AddAccount("AKlien", "Skeleton AKlien", "KAIB");

               


                // This is just to create some dummy prices - to demonstrate how to publish prices
                m_RunPX = true;
                m_PXThread = new Thread(new ThreadStart(this.runPXThread));
                m_PXThread.SetApartmentState(ApartmentState.MTA);
                m_PXThread.Start();

                // Report that we are open - this will show in the Driver view
                // in the dashboard
                SendStatusMessage(KaiTrade.Interfaces.Status.open, m_Name+":is open");
            }
            catch (Exception myE)
            {
                m_DriverLog.Error("DoStart", myE);
            }

        }

        /// <summary>
        /// This nethod is called when the system is being shutdown
        /// </summary>
        protected override void DoStop()
        {
            try
            {
                // Report that we are open - this will show in the Driver view
                // in the dashboard
                setStatus(KaiTrade.Interfaces.Status.closed);

                m_PublisherRegister.Clear();
            }
            catch (Exception myE)
            {
                m_DriverLog.Error("doStop", myE);
            }
        }

        #region products
        /// <summary>
        /// Shows you how to add products
        /// </summary>
        private void addProducts()
        {
            

            // If you have many products to add this AddDirect method using product data is 
            // much more effiecnt, this method also allows you to set more product information
            KaiTrade.TradeObjects.ProductData productData = new KaiTrade.TradeObjects.ProductData();
            productData.Mnemonic = "SKProd1";
            productData.Exchange = "CME";
            productData.SecurityID = "EPZ0";
            productData.IDSource = m_ID;
            productData.TradeVenue = m_ID;
            productData.DriverID = m_ID;
            productData.CFICode = KaiTrade.Interfaces.CFICode.FUTURE;
            productData.LongName = "S&P 500 eMini Dec";
            productData.TickSize = (decimal)0.25;
            this.AddProductDirect(productData);

            productData = new KaiTrade.TradeObjects.ProductData();
            productData.Mnemonic = "SKProd2";
            productData.Exchange = "CME";
            productData.SecurityID = "EPU0";
            productData.IDSource = m_ID;
            productData.TradeVenue = m_ID;
            productData.DriverID = m_ID;
            productData.CFICode = KaiTrade.Interfaces.CFICode.FUTURE;
            productData.LongName = "S&P 500 eMini Sep";
            productData.TickSize = (decimal)0.25;
            this.AddProductDirect(productData);
        }

        /// <summary>
        /// Request that a driver opens/subscribes to a product 
        /// </summary>
        /// <param name="myProduct">Tradable product - to get or request</param>
        /// <param name="myGenericName">The generic name for the product - i.e. not time sensitive
        ///  for example the S&P emini can be refered to as EP in CQG, it then may resolve to
        ///  EPM9, EPZ9  .... depending on which is the active front contract</param>
        public override void GetProduct(KaiTrade.Interfaces.TradableProduct myProduct, string myGenericName)
        {
            try
            {
                // you would normally request details from the broker or exchnage using the generic name - however lets hard code some values

                // get the product - it will usually be the same object as the one passed in, but to be sure...
                KaiTrade.Interfaces.TradableProduct product = m_Facade.Factory.GetProductManager().GetProductMnemonic(myProduct.Mnemonic);


                // note that the the GetProduct - with the generic needs almost all the details filled in
                // you should get these from the broker in some cases a GetProduct generic can result in 
                // more than one product returned from the exchnage in this case you would add the new products
                product.TickSize = (decimal)0.25;
                product.LongName = "A very long name from a generic";
                product.CFICode = "FXXXXX";
                product.TradeVenue = m_ID;
                product.SecurityID = "EP";



            }
            catch (Exception myE)
            {
                m_Log.Error("GetProduct", myE);
            }
        }

        /// <summary>
        /// Request the product details, get the driver to access the product and fill in 
        /// product details in the kaitrade product object.
        /// Note that not all drivers support this and that the call may take some
        /// time to set the values.
        /// </summary>
        /// <param name="myProduct"></param>
        public override void RequestProductDetails(KaiTrade.Interfaces.TradableProduct myProduct)
        {
            try
            {
                // you would normally request details from the broker or exchnage - however lets hard code some values

                // get the product - it will usually be the same object as the one passed in, but to be sure...
                KaiTrade.Interfaces.TradableProduct product = m_Facade.Factory.GetProductManager().GetProductMnemonic(myProduct.Mnemonic);

                product.TickSize = (decimal)0.25;
                product.LongName = "A very long name";


            }
            catch (Exception myE)
            {
                m_Log.Error("RequestProductDetails", myE);
            }
        }


        /// <summary>
        /// This method is called by the base class when some client registers for prices, you
        /// will normally subscribe to price from your broker at this point
        /// </summary>
        /// <param name="myPXPub"></param>
        protected override void SubscribeMD(KaiTrade.Interfaces.Publisher myPub, int depthLevels, string requestID)
        {
            try
            {
                // TO DO
                // In most cases you will need to store the PXPublisher against some request id(for prices) or a product name
                // so that when you get an upgrade back from the broker you can get the relevant PX publisher.
                //

            }
            catch (Exception myE)
            {
                m_Log.Error("SubscribeMD", myE);
            }
        }

        /// <summary>
        /// You will need to potentially need to unsuscribe from your broker prices here.
        /// </summary>
        /// <param name="myPXPub"></param>
        protected override void UnSubscribeMD(KaiTrade.Interfaces.Publisher myPub)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// This shows you how to publish prices - normally you would
        /// get these from your broker.
        /// </summary>
        private void runPXThread()
        {
            try
            {
                while (m_RunPX)
                {
                    // any client that wants prices will register with the BaseClass, in this loops we just 
                    // push random values into the publishers. In general, you would get an update from 
                    // the broker, create an update and then use ApplyPriceUpdate to notify any interested clients
                    //
                    foreach (string mnemonic in m_PublisherRegister.Keys)
                    {
                        int offset = m_random.Next(20);

                        //  THIS is the part where we do the price update
                        //
                        KaiTrade.Interfaces.PXUpdate pxUpdate = new KaiTrade.TradeObjects.PXUpdateBase("SKEL");
                        pxUpdate.Mnemonic = mnemonic;
                        pxUpdate.BidPrice = (decimal)101 + offset;
                        pxUpdate.BidSize = 20 + m_random.Next(9);
                        pxUpdate.OfferPrice = 102 + offset;
                        pxUpdate.OfferSize = 10 + m_random.Next(9);
                        pxUpdate.TradePrice = (pxUpdate.BidPrice + pxUpdate.OfferPrice) / 2;
                        pxUpdate.TradeVolume = (int)((pxUpdate.BidSize + pxUpdate.OfferSize) / 2);
                        pxUpdate.UpdateType = KaiTrade.Interfaces.PXUpdateType.none;
                        pxUpdate.DepthPosition = 0;
                        pxUpdate.ServerTicks = DateTime.Now.Ticks;

                        // This call pushes the update out to any clients
                        ApplyPriceUpdate(pxUpdate);


                    }
                    Thread.Sleep(200);
                }

            }
            catch (Exception myE)
            {

                m_DriverLog.Error("runPXThread", myE);

            }
        }

        #endregion

        /// <summary>
        /// Override the base driver DoSend - this is where we process 
        /// incomming requests from the system as a whole
        /// Simple drivers will only implement NewOrderSingle (D)
        /// </summary>
        /// <param name="myMsg"></param>
        protected override void DoSend(KaiTrade.Interfaces.Message myMsg)
        {
            try
            {
                lock (this)
                {
                    // Here are the typical FIX Messages you will receive to
                    // Support Order Routing
                    switch (myMsg.Label)
                    {
                        case "D":
                            submitOrder(myMsg);
                            break;
                        case "F":
                            // Pull or Cancel and Order
                            pullOrder(myMsg);
                            break;
                        case "G":
                            modifyOrder(myMsg);
                            break;
                        default:
                            // No Action - Discard Message
                            break;
                    }
                }


            }
            catch (Exception myE)
            {
                m_DriverLog.Error("Driver.Send", myE);
            }
        }

        /// <summary>
        /// FIX loop back order - this shows you hoe to parse an inbound order and send a 
        /// loop back execution report.
        /// In general this is where you will do the work of taking an order from KaiTrade (or the bus)
        /// and send it to your broker
        /// </summary>
        /// <param name="msg"></param>
        public void submitOrder(KaiTrade.Interfaces.Message myMsg)
        {
            QuickFix.Message myOrder = null;
            try
            {

                // Use QuickFix to handle the message - you can 
                // use the FIX parser of your own choice 
                myOrder = new QuickFix.Message(myMsg.Data);
                m_DriverLog.Error("SUBTEST:" + myMsg.Data);
                QuickFix.OrderQty orderQty = new QuickFix.OrderQty();
                // Get the QTY
                myOrder.getField(orderQty);
                int quantity = (int)orderQty.getValue();

                QuickFix.Price myPrice = new QuickFix.Price();
                double myOrdPrice = 99.0;
                if (myOrder.isSetField(myPrice))
                {
                    myOrder.getField(myPrice);
                    myOrdPrice = myPrice.getValue();
                }

                QuickFix.ClOrdID myClOrdID = new QuickFix.ClOrdID();
                myOrder.getField(myClOrdID);

                // You can optionally record order information in a context as follows - this context can then 
                // be retreived using ClOrdID, for example when doing a modify, cancel or execution report
                DriverBase.OrderContext myContext = new DriverBase.OrderContext();

                // Keep the original order message
                myContext.QFOrder = myOrder;
                myContext.ClOrdID = myClOrdID.getValue();
                myContext.OrderQty = quantity;
                myContext.CumQty = 0;
                
                // record the order in the context maps usng ClOrdID, note that you can also store the
                // context in m_ApiIDOrderMap - using the OrderID returned by the broker, or some other identifier they use
                m_ClOrdIDOrderMap.Add(myContext.ClOrdID, myContext);


                // put this into the internal book
                // send order in book exec report
                if (myOrdPrice == 0.0)
                {
                    myOrdPrice = 99.99;
                }
                QuickFix.OrdStatus myOrdStatus = new QuickFix.OrdStatus(QuickFix.OrdStatus.NEW);
                QuickFix.ExecType myExecType = new QuickFix.ExecType(QuickFix.ExecType.ORDER_STATUS);
                QuickFix.OrderID myOrderID = KaiUtil.Identities.Instance.genOrderID();

                sendExecReport(myOrder, myOrderID, myOrdStatus, myExecType, 0, quantity, 0, 0, 0);
                // put this into the internal book
                // send order in book exec report
                myOrdStatus = new QuickFix.OrdStatus(QuickFix.OrdStatus.FILLED);
                myExecType = new QuickFix.ExecType(QuickFix.ExecType.FILL);



                sendExecReport(myOrder, KaiUtil.Identities.Instance.genOrderID(), myOrdStatus, myExecType,
                                 (double)quantity, 0, quantity, myOrdPrice, myOrdPrice);


               


               
            }
            catch (Exception myE)
            {
                m_DriverLog.Error("submitOrder", myE);
                // To provide the end user with more information
                // send an advisory message, again this is optional
                // and depends on the adpater
                SendAdvisoryMessage("KTA Skeleton:submitOrder: problem submitting order:" + myE.ToString());

                QuickFix.OrdStatus myOrdStatus;
                QuickFix.ExecType myExecType = new QuickFix.ExecType(QuickFix.ExecType.REJECTED);

                myOrdStatus = new QuickFix.OrdStatus(QuickFix.OrdStatus.REJECTED);
                QuickFix.OrderQty orderQty = new QuickFix.OrderQty();
                if (myOrder != null)
                {
                    myOrder.getField(orderQty);
                    QuickFix.OrdRejReason myRejReason = new QuickFix.OrdRejReason(QuickFix.OrdRejReason.OTHER);
                    sendExecReport(myOrder, new QuickFix.OrderID("UNKNOWN"), myOrdStatus, myExecType, 0.0, (int)orderQty.getValue(), 0, 0, 0, myE.Message, myRejReason);
                }

            }
        }

        /// <summary>
        /// Example code of pulling an order - note this will delete any order we have processed
        /// irrespective of its state, the code is just to show how you can access the
        /// various data fields.
        /// </summary>
        /// <param name="msg"></param>
        public void pullOrder(KaiTrade.Interfaces.Message msg)
        {
            QuickFix.Message myQFPullOrder = null;
            try
            {
                // Extract the raw FIX Message from the inbound message
                string strOrder = msg.Data;

                // Use QuickFix to handle the message
                myQFPullOrder = new QuickFix.Message(strOrder);

                // Get the FIX id's these are mandatory
                QuickFix.ClOrdID clOrdID = new QuickFix.ClOrdID();
                QuickFix.OrigClOrdID origClOrdID = new QuickFix.OrigClOrdID();

                if (myQFPullOrder.isSetField(clOrdID))
                {
                    myQFPullOrder.getField(clOrdID);
                }
                else
                {
                    sendCancelRej(myQFPullOrder, QuickFix.CxlRejReason.UNKNOWN_ORDER, "a clordid must be specified on a cancel order");
                    Exception myE = new Exception("a clordid must be specified on a cancel order");
                    throw myE;
                }

                if (myQFPullOrder.isSetField(origClOrdID))
                {
                    myQFPullOrder.getField(origClOrdID);
                }
                else
                {
                    sendCancelRej(myQFPullOrder, QuickFix.CxlRejReason.UNKNOWN_ORDER, "a original clordid must be specified on a cancel order");
                    Exception myE = new Exception("an original clordid must be specified on a cancel order");
                    throw myE;
                }


                // You can extract the orderID if needed
                QuickFix.OrderID orderID = new QuickFix.OrderID();
                if (myQFPullOrder.isSetField(orderID))
                {
                    myQFPullOrder.getField(orderID);
                    string s = orderID.ToString();
                }
                else
                {
                    // you should have the order ID
                }

                // Get the context - we must have this to access the original order info order
                DriverBase.OrderContext myContext = null;
                if (m_ClOrdIDOrderMap.ContainsKey(origClOrdID.getValue()))
                {
                    myContext = m_ClOrdIDOrderMap[origClOrdID.getValue()];
                }
                if (myContext == null)
                {
                    sendCancelRej(myQFPullOrder, QuickFix.CxlRejReason.UNKNOWN_ORDER, "an order does not exist for the cancel requested");
                    Exception myE = new Exception("an order does not exist for the cancel requested");
                    throw myE;
                }

              

                // update the ClOrdID's on our order in the context
                QuickFix.ClOrdID newClordID = new QuickFix.ClOrdID(clOrdID.getValue());

                QuickFix.OrigClOrdID newOrigClOrdID = new QuickFix.OrigClOrdID(origClOrdID.getValue());
                myContext.QFOrder.setField(newClordID);
                myContext.QFOrder.setField(newOrigClOrdID);




                // send order canceled exec report
                QuickFix.OrdStatus myOrdStatus = new QuickFix.OrdStatus(QuickFix.OrdStatus.CANCELED);
                QuickFix.ExecType myExecType = new QuickFix.ExecType(QuickFix.ExecType.ORDER_STATUS);


                sendExecReport(myContext.QFOrder, new QuickFix.OrderID(myContext.OrderID), myOrdStatus, myExecType,
                                 0, myContext.LeavesQty, myContext.CumQty, 0, 0);




            }
            catch (Exception myE)
            {

                m_Log.Error("pullOrder", myE);
                // To provide the end user with more information
                // send an advisory message, again this is optional
                // and depends on the adpater
                SendAdvisoryMessage("pullOrder: problem pulling order:" + myE.ToString());

            }
        }

        /// <summary>
        /// Modify an order 
        /// </summary>
        /// <param name="msg"></param>
        public void modifyOrder(KaiTrade.Interfaces.Message msg)
        {
            QuickFix.Message myQFModOrder = null;
            try
            {
                // Extract the raw FIX Message from the inbound message
                string strOrder = msg.Data;

                // Use QuickFix to handle the message
                myQFModOrder = new QuickFix.Message(strOrder);

                // Get the FIX id's these are mandatory
                QuickFix.ClOrdID clOrdID = new QuickFix.ClOrdID();
                QuickFix.OrigClOrdID origClOrdID = new QuickFix.OrigClOrdID();

                if (myQFModOrder.isSetField(clOrdID))
                {
                    myQFModOrder.getField(clOrdID);
                }
                else
                {
                    sendCancelReplaceRej(myQFModOrder, QuickFix.CxlRejReason.UNKNOWN_ORDER, "a clordid must be specified on a modify order");
                    Exception myE = new Exception("a clordid must be specified on a modify order");
                    throw myE;
                }

                if (myQFModOrder.isSetField(origClOrdID))
                {
                    myQFModOrder.getField(origClOrdID);
                }
                else
                {
                    sendCancelReplaceRej(myQFModOrder, QuickFix.CxlRejReason.UNKNOWN_ORDER, "a original clordid must be specified on a modify order");
                    Exception myE = new Exception("an original clordid must be specified on a modify order");
                    throw myE;
                }

                // Get the context - we must have this to access the CQG order
                DriverBase.OrderContext myContext = null;
                if (m_ClOrdIDOrderMap.ContainsKey(origClOrdID.getValue()))
                {
                    myContext = m_ClOrdIDOrderMap[origClOrdID.getValue()];
                }
                if (myContext == null)
                {
                    sendCancelReplaceRej(myQFModOrder, QuickFix.CxlRejReason.UNKNOWN_ORDER, "an order does not exist for the modify requested");
                    Exception myE = new Exception("an order does not exist for the modify requested");
                    throw myE;
                }


                // modify the limit price
                QuickFix.Price newPrice = new QuickFix.Price();
                if (myQFModOrder.isSetField(newPrice))
                {
                    myQFModOrder.getField(newPrice);
                    myContext.QFOrder.setField(newPrice);


                }

                // modify the stop price
                QuickFix.StopPx newStopPx = new QuickFix.StopPx();
                if (myQFModOrder.isSetField(newStopPx))
                {
                    myQFModOrder.getField(newStopPx);
                    myContext.QFOrder.setField(newStopPx);


                }


                // modify the qtyqty
                QuickFix.OrderQty newOrderQty = new QuickFix.OrderQty();
                if (myQFModOrder.isSetField(newOrderQty))
                {
                    myQFModOrder.getField(newOrderQty);
                    myContext.OrderQty = (int)newOrderQty.getValue();
                    myContext.QFOrder.setField(newOrderQty);
                }



                // update the ClOrdID's on our order in the context
                QuickFix.ClOrdID newClordID = new QuickFix.ClOrdID(clOrdID.getValue());

                QuickFix.OrigClOrdID newOrigClOrdID = new QuickFix.OrigClOrdID(origClOrdID.getValue());
                myContext.QFOrder.setField(newClordID);
                myContext.QFOrder.setField(newOrigClOrdID);

                // record the context against the new clordid
                m_ClOrdIDOrderMap.Add(clOrdID.getValue(), myContext);

                // send order in book exec report
                QuickFix.OrdStatus myOrdStatus = new QuickFix.OrdStatus(QuickFix.OrdStatus.REPLACED);
                QuickFix.ExecType myExecType = new QuickFix.ExecType(QuickFix.ExecType.ORDER_STATUS);

                sendExecReport(myContext.QFOrder, new QuickFix.OrderID(myContext.OrderID), myOrdStatus, myExecType,
                                 0, myContext.LeavesQty, myContext.CumQty, 0, 0);

                

            }
            catch (Exception myE)
            {

                m_Log.Error("modifyOrder", myE);
                // To provide the end user with more information
                // send an advisory message, again this is optional
                // and depends on the adpater
                SendAdvisoryMessage("modifyOrder: problem modify order:" + myE.ToString());

            }
        }

       



    }
}
